Skip to content

boot.img是内核的镜像,不过该镜像并不只有内核的可执行文件,还有其他的信息,比如设备树等。但是有些嵌入式系统它的内核段分区也其他名称的,这里就不讨论定制的内核镜像格式。 内核镜像它生成后,最终会被uboot使用,所以我们从标准的uboot开始着手去探讨内核镜像如何的生成与制作。 这里的讲解以RK3588, kernel-6.6为参考进行分析。

U-Boot制作kernel镜像的工具

字数
1998 字
阅读时间
10 分钟

我们可以在官网下载最新的uboot源码: 接着解压,我们可以在目录u-boot-2024.10/tools中看到文件: 这里有mkimage工具源码。 所以说,内核的镜像格式,或者组织结构,是由uboot决定的。

FIT格式

我们可以在UBOOT DOC-Package中找到如下信息:

mkimage简单分析

略(未来有空完善)

示例1-RK3588的内核镜像

RK3588的内核镜像(boot.img)支持两种方式:

  • Android模式
  • FIT模式

Android模式

在你使用./build.sh kernel(实际上是make ARCH=arm64 kernel_source)时,在arch/arm64/Makefile中有如下代码:

makefile
%.img: rockchip/%.dtb kernel.img $(LOGO) $(LOGO_KERNEL)
	$(Q)$(srctree)/scripts/mkimg --dtb $*.dtb

RK3588代码中有一个制作Android 启动内核的工具:scripts/mkimg

shell
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2019 Fuzhou Rockchip Electronics Co., Ltd.

......

OUT=out
ITB=${BOOT_IMG}
ITS=${OUT}/boot.its
MKIMAGE=${MKIMAGE-"mkimage"}
MKIMAGE_ARG="-E -p 0x800"

make_boot_img()
{
	RAMDISK_IMG_PATH=${objtree}/ramdisk.img
	echo "RAMDISK_IMG_PATH=${RAMDISK_IMG_PATH}, PWD=$(pwd)"
	[ -f ${RAMDISK_IMG_PATH} ] && RAMDISK_IMG=ramdisk.img && RAMDISK_ARG="--ramdisk ${RAMDISK_IMG_PATH}"

	${srctree}/scripts/mkbootimg \
		${KERNEL_IMAGE_ARG} \
		${RAMDISK_ARG} \
		--second resource.img \
		-o boot.img && \
	echo "  Image:  boot.img (with Image ${RAMDISK_IMG} resource.img) is ready";
	${srctree}/scripts/mkbootimg \
		${KERNEL_ZIMAGE_ARG} \
		${RAMDISK_ARG} \
		--second resource.img \
		-o zboot.img && \
	echo "  Image:  zboot.img (with ${ZIMAGE} ${RAMDISK_IMG} resource.img) is ready"
}

......

scripts/resource_tool ${DTB_PATH} ${LOGO} ${LOGO_KERNEL} >/dev/null
echo "  Image:  resource.img (with ${DTB} ${LOGO} ${LOGO_KERNEL}) is ready"

echo "BOOT_IMG=${BOOT_IMG}"
echo "BOOT_ITS=${BOOT_ITS}"
if [ -f "${BOOT_IMG}" ]; then
	if file -L -p -b ${BOOT_IMG} | grep -q 'Device Tree Blob' ; then
		repack_itb;
	elif [ -x ${srctree}/scripts/repack-bootimg ]; then
		repack_boot_img;
	fi
elif [ -f "${BOOT_ITS}" ]; then
	make_fit_boot_img;
elif [ -x ${srctree}/scripts/mkbootimg ]; then
	echo "running make_boot_img"
	make_boot_img;
fi

这里有两个指令:

shell
${srctree}/scripts/mkbootimg \
	${KERNEL_IMAGE_ARG} \
	${RAMDISK_ARG} \
	--second resource.img \
	-o boot.img

${srctree}/scripts/mkbootimg \
	${KERNEL_ZIMAGE_ARG} \
	${RAMDISK_ARG} \
	--second resource.img \
	-o zboot.img

mkbootimg是一个Android的构建工具,我们可以在Android mkbootimg.py源码这里获取。接着我们来看看该工具生成的boot.img是怎么样的结构。 我们在bootimg.h中可以看到其结构注释如下:

c
/* When a boot header is of version 0, the structure of boot image is as
 * follows:
 *
 * +-----------------+
 * | boot header     | 1 page
 * +-----------------+
 * | kernel          | n pages
 * +-----------------+
 * | ramdisk         | m pages
 * +-----------------+
 * | second stage    | o pages
 * +-----------------+
 *
 * n = (kernel_size + page_size - 1) / page_size
 * m = (ramdisk_size + page_size - 1) / page_size
 * o = (second_size + page_size - 1) / page_size
 *
 * 0. all entities are page_size aligned in flash
 * 1. kernel and ramdisk are required (size != 0)
 * 2. second is optional (second_size == 0 -> no second)
 * 3. load each element (kernel, ramdisk, second) at
 *    the specified physical address (kernel_addr, etc)
 * 4. prepare tags at tag_addr.  kernel_args[] is
 *    appended to the kernel commandline in the tags.
 * 5. r0 = 0, r1 = MACHINE_TYPE, r2 = tags_addr
 * 6. if second_size != 0: jump to second_addr
 *    else: jump to kernel_addr
 */

为了验证是否正确,我们可以进行如下操作,先在build.sh(你的编译脚本可能不是这个)中阻止mk-fitimage.sh覆盖boot.img,这里在mkimage打包后,立马停止打包。这样我们就可以得到一个Android格式的boot.img

shell
build_kernel()
{
......
	echo "============Start building kernel============"
	echo "TARGET_KERNEL_ARCH   =$RK_KERNEL_ARCH"
	echo "TARGET_KERNEL_CONFIG =$RK_KERNEL_DEFCONFIG"
	echo "TARGET_KERNEL_DTS    =$RK_KERNEL_DTS"
	echo "TARGET_KERNEL_CONFIG_FRAGMENT =$RK_KERNEL_DEFCONFIG_FRAGMENT"
	echo "=========================================="
......
	$KMAKE $RK_KERNEL_DTS.img -j1
	exit 0 #增加这句

	ITS="$CHIP_DIR/$RK_KERNEL_FIT_ITS"
	if [ -f "$ITS" ]; then
		$COMMON_DIR/mk-fitimage.sh kernel/$RK_BOOT_IMG \
			"$ITS" $RK_KERNEL_IMG
	fi
}

我们将内核目录下生成的boot.img进行解压,在此前,我们需要安装工具unpackbootimg:

shell
git clone https://github.com/osm0sis/mkbootimg
cd mkbootimg
make
sudo make install

解压:

shell
mkdir bootimg_out
unpackbootimg -i ./boot.img -o ./bootimg_out

上面得知,magicANDROID!,这个是boot header,他有不同的版本,我们可以参考Android boot镜像Header来分析,这里我实际的应该是v1或者v2。同时我们使用Hex Editor查看该boot.img: kernel size(小端模式)arch/arm64/boot/Image的大小比较,基本一致。 接着我们偏移1个page_size,查看内核Image的内容: 内核可执行文件Image被放置到1 page_size位置,和bootimg.h中指示的一样。如果我们直接烧录该boot.img,启动时会有如下log: 这个证明Android镜像可以成功被加载。

FIT模式

我们在build.sh中找到如下代码(和上面的build.sh有些差异,但基本意思不变):

shell
cd kernel
make ARCH=$RK_ARCH $RK_KERNEL_DEFCONFIG $RK_KERNEL_DEFCONFIG_FRAGMENT
make ARCH=$RK_ARCH $RK_KERNEL_DTS.img -j$RK_JOBS
if [ -f "$TOP_DIR/device/rockchip/$RK_TARGET_PRODUCT/$RK_KERNEL_FIT_ITS" ]; then
	$COMMON_DIR/mk-fitimage.sh $TOP_DIR/kernel/$RK_BOOT_IMG \
		$TOP_DIR/device/rockchip/$RK_TARGET_PRODUCT/$RK_KERNEL_FIT_ITS \
		$TOP_DIR/kernel/ramdisk.img
fi

这里会检查$TOP_DIR/device/rockchip/$RK_TARGET_PRODUCT/$RK_KERNEL_FIT_ITS是否存在,而预设的变量RK_KERNEL_FIT_ITSRK_BOOT_IMG等会放在软链接指示的地方: 所以上面的命令是判断是否在RK3588_SOURCE/device/rockchip/.target_product下是否有boot.its文件,如果有则使用FIT 这里的boot.its只是提供一个模板,实际上会在mk-fitimages.sh中使用sed进行替换,比如某次替换结果如下:

shell
/*
 * Copyright (C) 2021 Rockchip Electronics Co., Ltd
 *
 * SPDX-License-Identifier: GPL-2.0
 */

/dts-v1/;
/ {
    description = "U-Boot FIT source file for arm";

    images {
        fdt {
            data = /incbin/("/home/xxx/RK3588/kernel-6.6.29-git/arch/arm64/boot/dts/rockchip/rk3588-evb7-lp4-v10-linux.dtb");
            type = "flat_dt";
            arch = "arm64";
            compression = "none";
            load = <0xffffff00>;

            hash {
                algo = "sha256";
            };
        };

        kernel {
            data = /incbin/("/home/xxx/RK3588/kernel-6.6.29-git/arch/arm64/boot/Image");
            type = "kernel";
            arch = "arm64";
            os = "linux";
            compression = "none";
            entry = <0xffffff01>;
            load = <0xffffff01>;

            hash {
                algo = "sha256";
            };
        };

        resource {
            data = /incbin/("/home/xxx/RK3588/kernel-6.6.29-git/resource.img");
            type = "multi";
            arch = "arm64";
            compression = "none";

            hash {
                algo = "sha256";
            };
        };
    };

    configurations {
        default = "conf";

        conf {
            rollback-index = <0x00>;
            fdt = "fdt";
            kernel = "kernel";
            multi = "resource";

            signature {
                algo = "sha256,rsa2048";
                padding = "pss";
                key-name-hint = "dev";
                sign-images = "fdt", "kernel", "multi";
            };
        };
    };
};

打包成功后有如下打印: 我们烧录后,启动uboot时:

示例2-A40i的加载方式

参考链接

Android boot镜像头Android boot分区mkimage用法Android源码Android mkbootimg.py源码安卓boot镜像解析-博客园安卓boot类镜像解析-CSDNhttp://nerveware.org/device-and-image-trees/1.htmlhttps://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18841676/U-Boot+Flattened+Device+Treehttp://www.wowotech.net/u-boot/fit_image_overview.htmlhttps://docs.u-boot.org/en/stable/usage/fit/index.html

贡献者

The avatar of contributor named as Px Px

页面历史

撰写